home *** CD-ROM | disk | FTP | other *** search
Text File | 1997-01-17 | 53.5 KB | 1,491 lines | [TEXT/R*ch] |
- Archive-name: C++-faq/part4
- Posting-Frequency: monthly
- Last-modified: Jan 1, 1997
- URL: http://www.cerfnet.com/~mpcline/c++-faq-lite/
-
- AUTHOR: Marshall Cline / cline@parashift.com / Paradigm Shift, Inc. /
- One Park St. / Norwood, NY 13668 / 315-353-6100 (voice) / 315-353-6110 (fax)
-
- COPYRIGHT: This posting is part of "C++ FAQ Lite." The entire "C++ FAQ Lite"
- document is Copyright(C) 1991-96 Marshall P. Cline, Ph.D., cline@parashift.com.
- All rights reserved. Copying is permitted only under designated situations.
- For details, see section [1].
-
- NO WARRANTY: THIS WORK IS PROVIDED ON AN "AS IS" BASIS. THE AUTHOR PROVIDES NO
- WARRANTY WHATSOEVER, EITHER EXPRESS OR IMPLIED, REGARDING THE WORK, INCLUDING
- WARRANTIES WITH RESPECT TO ITS MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR
- PURPOSE.
-
- C++-FAQ-Lite != C++-FAQ-Book: This document, C++ FAQ Lite, is not the same as
- the C++ FAQ Book. The book (C++ FAQs, Cline and Lomow, Addison-Wesley) is 500%
- larger than this document, and is available in bookstores. For details, see
- section [3].
-
- ==============================================================================
-
- SECTION [14]: Friends
-
-
- [14.1] What is a friend?
-
- Something to allow your class to grant access to another class or function.
-
- Friends can be either functions or other classes. A class grants access
- privileges to its friends. Normally a developer has political and technical
- control over both the friend and member functions of a class (else you may need
- to get permission from the owner of the other pieces when you want to update
- your own class).
-
- ==============================================================================
-
- [14.2] Do friends violate encapsulation?
-
- If they're used properly, they actually enhance encapsulation.
-
- You often need to split a class in half when the two halves will have different
- numbers of instances or different lifetimes. In these cases, the two halves
- usually need direct access to each other (the two halves used to be in the same
- class, so you haven't increased the amount of code that needs direct access to
- a data structure; you've simply reshuffled the code into two classes instead of
- one). The safest way to implement this is to make the two halves friends of
- each other.
-
- If you use friends like just described, you'll keep private things private.
- People who don't understand this often make naive efforts to avoid using
- friendship in situations like the above, and often they actually destroy
- encapsulation. They either use public data (grotesque!), or they make the data
- accessible between the halves via public get() and set() member functions.
- Having a public get() and set() member function for a private datum is OK only
- when the private datum "makes sense" from outside the class (from a user's
- perspective). In many cases, these get()/set() member functions are almost as
- bad as public data: they hide (only) the name of the private datum, but they
- don't hide the existence of the private datum.
-
- Similarly, if you use friend functions as a syntactic variant of a class's
- public: access functions, they don't violate encapsulation any more than a
- member function violates encapsulation. In other words, a class's friends
- don't violate the encapsulation barrier: along with the class's member
- functions, they are the encapsulation barrier.
-
- ==============================================================================
-
- [14.3] What are some advantages/disadvantages of using friend functions?
-
- They provide a degree of freedom in the interface design options.
-
- Member functions and friend functions are equally privileged (100% vested).
- The major difference is that a friend function is called like f(x), while a
- member function is called like x.f(). Thus the ability to choose between
- member functions (x.f()) and friend functions (f(x)) allows a designer to
- select the syntax that is deemed most readable, which lowers maintenance costs.
-
- The major disadvantage of friend functions is that they require an extra line
- of code when you want dynamic binding. To get the effect of a virtual friend,
- the friend function should call a hidden (usually protected:) virtual[20]
- member function. This is called the Virtual Friend Function Idiom[15.8]. For
- example:
-
- class Base {
- public:
- friend void f(Base& b);
- // ...
- protected:
- virtual void do_f();
- // ...
- };
-
- inline void f(Base& b)
- {
- b.do_f();
- }
-
- class Derived : public Base {
- public:
- // ...
- protected:
- virtual void do_f(); // "Override" the behavior of f(Base& b)
- // ...
- };
-
- void userCode(Base& b)
- {
- f(b);
- }
-
- The statement f(b) in userCode(Base&) will invoke b.do_f(), which is
- virtual[20]. This means that Derived::do_f() will get control if b is actually
- a object of class Derived. Note that Derived overrides the behavior of the
- protected: virtual[20] member function do_f(); it does not have its own
- variation of the friend function, f(Base&).
-
- ==============================================================================
-
- [14.4] What does it mean that "friendship is neither inherited nor transitive"?
-
- I may declare you as my friend, but that doesn't mean I necessarily trust
- either your kids or your friends.
- * I don't necessarily trust the kids of my friends. The privileges of
- friendship aren't inherited. Derived classes of a friend aren't necessarily
- friends. If class Fred declares that class Base is a friend, classes
- derived from Base don't have any automatic special access rights to Fred
- objects.
- * I don't necessarily trust the friends of my friends. The privileges of
- friendship aren't transitive. A friend of a friend isn't necessarily a
- friend. If class Fred declares class Wilma as a friend, and class Wilma
- declares class Betty as a friend, class Betty doesn't necessarily have any
- special access rights to Fred objects.
-
- ==============================================================================
-
- [14.5] Should my class declare a member function or a friend function?
-
- Use a member when you can, and a friend when you have to.
-
- Sometimes friends are syntactically better (e.g., in class Fred, friend
- functions allow the Fred parameter to be second, while members require it to be
- first). Another good use of friend functions are the binary infix arithmetic
- operators. E.g., aComplex + aComplex should be defined as a friend rather than
- a member if you want to allow aFloat + aComplex as well (member functions don't
- allow promotion of the left hand argument, since that would change the class of
- the object that is the recipient of the member function invocation).
-
- In other cases, choose a member function over a friend function.
-
- ==============================================================================
-
- SECTION [15]: Input/output via <iostream.h> and <stdio.h>
-
-
- [15.1] Why should I use <iostream.h> instead of the traditional <stdio.h>?
-
- Increase type safety, reduce errors, improve performance, allow extensibility,
- and provide subclassability.
-
- printf() is arguably not broken, and scanf() is perhaps livable despite being
- error prone, however both are limited with respect to what C++ I/O can do. C++
- I/O (using << and >>) is, relative to C (using printf() and scanf()):
- * Better type safety: With <iostream.h>, the type of object being I/O'd is
- known statically by the compiler. In contrast, <stdio.h> uses "%" fields to
- figure out the types dynamically.
- * Less error prone: With <iostream.h>, there are no redundant "%" tokens that
- have to be consistent with the actual objects being I/O'd. Removing
- redundancy removes a class of errors.
- * Extensible: The C++ <iostream.h> mechanism allows new user-defined types to
- be I/O'd without breaking existing code. Imagine the chaos if everyone was
- simultaneously adding new incompatible "%" fields to printf() and
- scanf()?!).
- * Subclassable: The C++ <iostream.h> mechanism is built from real classes such
- as ostream and istream. Unlike <stdio.h>'s FILE*, these are real classes
- and hence subclassable. This means you can have other user-defined things
- that look and act like streams, yet that do whatever strange and wonderful
- things you want. You automatically get to use the zillions of lines of I/O
- code written by users you don't even know, and they don't need to know about
- your "extended stream" class.
-
- ==============================================================================
-
- [15.2] Why does my program go into an infinite loop when someone enters an
- invalid input character? [NEW!]
-
- [Recently created (on 1/97).]
-
- For example, suppose you have the following code that reads integers from cin:
-
- #include <iostream.h>
-
- main()
- {
- cout << "Enter numbers separated by whitespace (use -1 to quit): ";
- int i = 0;
- while (i != -1) {
- cin >> i; // BAD FORM -- See comments below
- cout << "You entered " << i << '\n';
- }
- }
-
- The problem with this code is that it lacks any checking to see if someone
- entered an invalid input character. In particuluar, if someone enters
- something that doesn't look like an integer (such as an 'x'), the stream cin
- goes into a "failed state," and all subsequent input attempts return
- immediately without doing anything. In other words, the program enters an
- infinite loop; if 42 was the last number that was successfully read, the
- program will print the message You entered 42 over and over.
-
- An easy way to check for invalid input is to move the input request from the
- body of the while loop into the control-expression of the while loop. E.g.,
-
- #include <iostream.h>
-
- main()
- {
- cout << "Enter a number, or -1 to quit: ";
- int i = 0;
- while (cin >> i) { // GOOD FORM
- if (i == -1) break;
- cout << "You entered " << i << '\n';
- }
- }
-
- This will cause the while loop to exit either when you hit end-of-file, or when
- you enter a bad integer, or when you enter -1.
-
- (Naturally you can eliminate the break by changing the while loop expression
- from while (cin >> i) to while ((cin >> i) && (i != -1)), but that's not really
- the point of this FAQ since this FAQ has to do with iostreams rather than
- generic structured programming guidelines.)
-
- ==============================================================================
-
- [15.3] How does that funky while (cin >> foo) syntax work? [NEW!]
-
- [Recently created (on 1/97).]
-
- See the previous FAQ[15.2] for an example of the "funky while (cin >> foo)
- syntax."
-
- The expression (cin >> foo) calls the appropriate operator>> (for example, it
- calls the operator>> that takes an istream on the left and, if foo is of type
- int, an int& on the right). The istream operator>> functions return their left
- argument by convention, which in this case means it will return cin. Next the
- compiler notices that the returned istream is in a boolean context, so it calls
- the "cast" operator istream::operator bool(). I.e., in this case, it calls
- cin.operator bool(), just as if you had casted it explicitly such as (bool)cin
- or bool(cin). (Note: if your compiler doesn't yet support the bool type,
- istream::operator void*() will be called instead.)
-
- The operator bool() cast operator returns true if the stream is in a good
- state, or false if it's in a failed state (in the void* case, the return values
- will be some non-NULL pointer or the NULL pointer, respectively). For example,
- if you read one too many times (e.g., if you're already at end-of-file), or if
- the actual info on the input stream isn't valid for the type of foo (e.g., if
- foo is an int and the data is an 'x' character), the stream will go into a
- failed state and the cast operator will return false.
-
- The reason operator>> doesn't simply return a bool indicating whether it
- succeeded or failed is to support the "cascading" syntax:
-
- cin >> foo >> bar;
-
- The operator>> is left-associative, which means the above is parsed as:
-
- (cin >> foo) >> bar;
-
- In other words, if we replace operator>> with a normal function name such as
- readFrom(), this becomes the expression:
-
- readFrom( readFrom(cin, foo), bar);
-
- As always, we begin evaluating at the innermost expression. Because of the
- left-associativity of operator>>, this happens to be the left-most expression,
- cin >> foo. This expression returns cin (more precisely, it returns a
- reference to its left-hand argument) to the next expression. The next
- expression also returns (a reference to) cin, but this second reference is
- ignored since it's the outermost expression in this "expression statement."
-
- ==============================================================================
-
- [15.4] Why does my input seem to process past the end of file?
-
- Because the eof state is not set until after a read is attempted past the end
- of file. That is, reading the last byte from a file does not set the eof
- state.
-
- For example, the following code has an off-by-one error with the count i:
-
- int i = 0;
- while (! cin.eof()) { // WRONG!
- cin >> x;
- ++i;
- // Work with x ...
- }
-
- What you really need is:
-
- int i = 0;
- while (cin >> x) { // RIGHT!
- ++i;
- // Work with x ...
- }
-
- ==============================================================================
-
- [15.5] Why is my program ignoring my input request after the first iteration?
-
- Because the numerical extractor leaves non-digits behind in the input buffer.
-
- If your code looks like this:
-
- char name[1000];
- int age;
-
- for (;;) {
- cout << "Name: ";
- cin >> name;
- cout << "Age: ";
- cin >> age;
- }
-
- What you really want is:
-
- for (;;) {
- cout << "Name: ";
- cin >> name;
- cout << "Age: ";
- cin >> age;
- cin.ignore(INT_MAX, '\n');
- }
-
- ==============================================================================
-
- [15.6] How can I provide printing for my class Fred? [UPDATED!]
-
- [Recently added note about cascading operator<< calls (on 1/97).]
-
- Use operator overloading[13] to provide a friend[14] left-shift operator,
- operator<<.
-
- #include <iostream.h>
-
- class Fred {
- public:
- friend ostream& operator<< (ostream& o, const Fred& fred);
- // ...
- private:
- int i_; // Just for illustration
- };
-
- ostream& operator<< (ostream& o, const Fred& fred)
- {
- return o << fred.i_;
- }
-
- main()
- {
- Fred f;
- cout << "My Fred object: " << f << "\n";
- }
-
- We use a friend[14] rather than a member since the Fred parameter is second
- rather than first.
-
- Note that operator<< returns the stream. This is so the output operations can
- be cascaded[15.3].
-
- ==============================================================================
-
- [15.7] How can I provide input for my class Fred? [UPDATED!]
-
- [Recently added note about cascading operator<< calls (on 1/97).]
-
- Use operator overloading[13] to provide a friend[14] right-shift operator,
- operator>>. This is similar to the output operator[15.6], except the parameter
- doesn't have a const[18]: "Fred&" rather than "const Fred&".
-
- #include <iostream.h>
-
- class Fred {
- public:
- friend istream& operator>> (istream& i, Fred& fred);
- // ...
- private:
- int i_; // Just for illustration
- };
-
- istream& operator>> (istream& i, Fred& fred)
- {
- return i >> fred.i_;
- }
-
- main()
- {
- Fred f;
- cout << "Enter a Fred object: ";
- cin >> f;
- // ...
- }
-
- Note that operator>> returns the stream. This is so the input operations can
- be cascaded and/or used in a while loop or if statement[15.3].
-
- ==============================================================================
-
- [15.8] How can I provide printing for an entire hierarchy of classes?
-
- Provide a friend[14] operator<<[15.6] that calls a protected virtual[20]
- function:
-
- class Base {
- public:
- friend ostream& operator<< (ostream& o, const Base& b);
- // ...
- protected:
- virtual void print(ostream& o) const;
- };
-
- inline ostream& operator<< (ostream& o, const Base& b)
- {
- b.print(o);
- return o;
- }
-
- class Derived : public Base {
- protected:
- virtual void print(ostream& o) const;
- };
-
- The end result is that operator<< acts as if it was dynamically bound, even
- though it's a friend[14] function. This is called the Virtual Friend Function
- Idiom.
-
- Note that derived classes override print(ostream&) const. In particular, they
- do not provide their own operator<<.
-
- Naturally if Base is an ABC[22.3], Base::print(ostream&) const can be declared
- pure virtual[22.4] using the "= 0" syntax.
-
- ==============================================================================
-
- [15.9] How can I "reopen" cin and cout in binary mode under DOS and/or OS/2?
-
- This is implementation dependent. Check with your compiler's documentation.
-
- For example, suppose you want to do binary I/O using cin and cout. Suppose
- further that your operating system (such as DOS or OS/2) insists on translating
- "\r\n" into "\n" on input from cin, and "\n" to "\r\n" on output to cout or
- cerr.
-
- Unfortunately there is no standard way to cause cin, cout, and/or cerr to be
- opened in binary mode. Closing the streams and attempting to reopen them in
- binary mode might have unexpected or undesirable results.
-
- On systems where it makes a difference, the implementation might provide a way
- to make them binary streams, but you would have to check the manuals to find
- out.
-
- ==============================================================================
-
- [15.10] Why can't I open a file in a different directory such as "..\test.dat"?
-
- Because "\t" is a tab character.
-
- You should use forward slashes in your filenames, even on an operating system
- that uses backslashes such as DOS, Windows, OS/2, etc. For example:
-
- #include <iostream.h>
- #include <fstream.h>
-
- main()
- {
- #if 1
- ifstsream file("../test.dat"); // RIGHT!
- #else
- ifstsream file("..\test.dat"); // WRONG!
- #endif
-
- // ...
- }
-
- Remember, the backslash ("\") is used in string literals to create special
- characters: "\n" is a newline, "\b" is a backspace, and "\t" is a tab, "\a" is
- an "alert", "\v" is a vertical-tab, etc. Therefore the file name
- "\version\next\alpha\beta\test.dat" is interpreted as a bunch of very funny
- characters; use "/version/next/alpha/beta/test.dat" instead, even on systems
- that use a "\" as the directory separator such as DOS, Windows, OS/2, etc.
-
- ==============================================================================
-
- SECTION [16]: Freestore management
-
-
- [16.1] Does delete p delete the pointer p, or the pointed-to-data *p?
-
- The pointed-to-data.
-
- The keyword should really be delete_the_thing_pointed_to_by. The same abuse of
- English occurs when freeing the memory pointed to by a pointer in C: free(p)
- really means free_the_stuff_pointed_to_by(p).
-
- ==============================================================================
-
- [16.2] Can I free() pointers allocated with new? Can I delete pointers
- allocated with malloc()?
-
- No!
-
- It is perfectly legal, moral, and wholesome to use malloc() and delete in the
- same program, or to use new and free() in the same program. But it is illegal,
- immoral, and despicable to call free() with a pointer allocated via new, or to
- call delete on a pointer allocated via malloc().
-
- Beware! I occasionally get e-mail from people telling me that it works OK for
- them on machine X and compiler Y. That does not make it right! Sometimes
- people say, "But I'm just working with an array of char." Nonetheless do not
- mix malloc() and delete on the same pointer, or new and free() on the same
- pointer! If you allocated via p = new char[n], you must use delete[] p; you
- must not use free(p). Or if you allocated via p = malloc(n), you must use
- free(p); you must not use delete[] p or delete p! Mixing these up could cause a
- catastrophic failure at runtime if the code was ported to a new machine, a new
- compiler, or even a new version of the same compiler.
-
- You have been warned.
-
- ==============================================================================
-
- [16.3] Why should I use new instead of trustworthy old malloc()?
-
- Constructors/destructors, type safety, overridability.
- * Constructors/destructors: unlike malloc(sizeof(Fred)), new Fred() calls
- Fred's constructor. Similarly, delete p calls *p's destructor.
- * Type safety: malloc() returns a void* which isn't type safe. new Fred()
- returns a pointer of the right type (a Fred*).
- * Overridability: new is an operator that can be overridden by a class, while
- malloc() is not overridable on a per-class basis.
-
- ==============================================================================
-
- [16.4] Can I use realloc() on pointers allocated via new?
-
- No!
-
- When realloc() has to copy the allocation, it uses a bitwise copy operation,
- which will tear many C++ objects to shreds. C++ objects should be allowed to
- copy themselves. They use their own copy constructor or assignment operator.
-
- Besides all that, the heap that new uses may not be the same as the heap that
- malloc() and realloc() use!
-
- ==============================================================================
-
- [16.5] Do I need to check for NULL after p = new Fred()?
-
- No! (But if you have an old compiler, you may have to force the compiler to
- have this behavior[16.6]).
-
- It turns out to be a real pain to always write explicit NULL tests after every
- new allocation. Code like the following is very tedious:
-
- Fred* p = new Fred();
- if (p == NULL)
- throw bad_alloc();
-
- If your compiler doesn't support (or if you refuse to use) exceptions[17], your
- code might be even more tedious:
-
- Fred* p = new Fred();
- if (p == NULL) {
- cerr << "Couldn't allocate memory for a Fred" << endl;
- abort();
- }
-
- Take heart. In C++, if the runtime system cannot allocate sizeof(Fred) bytes
- of memory during p = new Fred(), a bad_alloc exception will be thrown. Unlike
- malloc(), new never returns NULL!
-
- Therefore you should simply write:
-
- Fred* p = new Fred(); // No need to check if p is NULL
-
- However, if your compiler is old, it may not yet support this. Find out by
- checking your compiler's documentation under "new". If you have an old
- compiler, you may have to force the compiler to have this behavior[16.6].
-
- ==============================================================================
-
- [16.6] How can I convince my (older) compiler to automatically check new to see
- if it returns NULL? [UPDATED!]
-
- [Recently fixed bugs: new handlers don't take arguments, thanks to Scott Aaron;
- changed set_new_hanlder to set_new_handler, thanks to Peter Andersson (on
- 1/97).]
-
- Eventually your compiler will.
-
- If you have an old compiler that doesn't automagically perform the NULL
- test[16.5], you can force the runtime system to do the test by installing a
- "new handler" function. Your "new handler" function can do anything you want,
- such as print a message and abort() the program, delete some objects and return
- (in which case operator new will retry the allocation), throw an exception,
- etc.
-
- Here's a sample "new handler" that prints a message and calls abort(). The
- handler is installed using set_new_handler():
-
- #include <new.h> // To get set_new_handler
- #include <stdlib.h> // To get abort()
- #include <iostream.h> // To get cerr
-
- void myNewHandler()
- {
- // This is your own handler. It can do anything you want.
- cerr << "Attempt to allocate memory failed!" << endl;
- abort();
- }
-
- main()
- {
- set_new_handler(myNewHandler); // Install your "new handler"
- // ...
- }
-
- After the set_new_handler() line is executed, operator new will call your
- myNewHandler() if/when it runs out of memory. This means that new will never
- return NULL:
-
- Fred* p = new Fred(); // No need to check if p is NULL
-
- Note: Please use this abort() approach as a last resort. If your compiler
- supports exception handling[17], please consider throwing an exception instead
- of calling abort().
-
- Note: If some global/static object's constructor uses new, it won't use the
- myNewHandler() function since that constructor will get called before main()
- begins. Unfortunately there's no convenient way to guarantee that the
- set_new_handler() will be called before the first use of new. For example,
- even if you put the set_new_handler() call in the constructor of a global
- object, you still don't know if the module ("compilation unit") that contains
- that global object will be elaborated first or last or somewhere inbetween.
- Therefore you still don't have any guarantee that your call of
- set_new_handler() will happen before any other global's constructor gets
- invoked.
-
- ==============================================================================
-
- [16.7] Do I need to check for NULL before delete p?
-
- No!
-
- The C++ language guarantees that delete p will do nothing if p is equal to
- NULL. Since you might get the test backwards, and since most testing
- methodologies force you to explicitly test every branch point, you should not
- put in the redundant if test.
-
- Wrong:
-
- if (p != NULL)
- delete p;
-
- Right:
-
- delete p;
-
- ==============================================================================
-
- [16.8] What are the two steps that happen when I say delete p?
-
- delete p is a two-step process: it calls the destructor, then releases the
- memory. The code generated for delete p looks something like this (assuming p
- is of type Fred*):
-
- // Original code: delete p;
- if (p != NULL) {
- p->~Fred();
- operator delete(p);
- }
-
- The statement p->~Fred() calls the destructor for the Fred object pointed to by
- p.
-
- The statement operator delete(p) calls the memory deallocation primitive,
- void operator delete(void* p). This primitive is similar in spirit to
- free(void* p). (Note, however, that these two are not interchangeable; e.g.,
- there is no guarantee that the two memory deallocation primitives even use the
- same heap!).
-
- ==============================================================================
-
- [16.9] In p = new Fred(), does the Fred memory "leak" if the Fred constructor
- throws an exception?
-
- No.
-
- If an exception occurs during the Fred constructor of p = new Fred(), the C++
- language guarantees that the memory sizeof(Fred) bytes that were allocated will
- automagically be released back to the heap.
-
- Here are the details: new Fred() is a two-step process:
-
- 1. sizeof(Fred) bytes of memory are allocated using the primitive
- void* operator new(size_t nbytes). This primitive is similar in spirit to
- malloc(size_t nbytes). (Note, however, that these two are not
- interchangeable; e.g., there is no guarantee that the two memory allocation
- primitives even use the same heap!).
-
- 2. It constructs an object in that memory by calling the Fred constructor.
- The pointer returned from the first step is passed as the this parameter to
- the constructor. This step is wrapped in a try ... catch block to handle
- the case when an exception is thrown during this step.
-
- Thus the actual generated code looks something like:
-
- // Original code: Fred* p = new Fred();
- Fred* p = (Fred*) operator new(sizeof(Fred));
- try {
- new(p) Fred(); // Placement new[11.10]
- } catch (...) {
- operator delete(p); // Deallocate the memory
- throw; // Re-throw the exception
- }
-
- The statement marked "Placement new[11.10]" calls the Fred constructor. The
- pointer p becomes the this pointer inside the constructor, Fred::Fred().
-
- ==============================================================================
-
- [16.10] How do I allocate / unallocate an array of things?
-
- Use p = new T[n] and delete[] p:
-
- Fred* p = new Fred[100];
- // ...
- delete[] p;
-
- Any time you allocate an array of objects via new (usually with the [n] in the
- new expression), you must use [] in the delete statement. This syntax is
- necessary because there is no syntactic difference between a pointer to a thing
- and a pointer to an array of things (something we inherited from C).
-
- ==============================================================================
-
- [16.11] What if I forget the [] when deleteing array allocated via new T[n]?
-
- All life comes to a catastrophic end.
-
- It is the programmer's --not the compiler's-- responsibility to get the
- connection between new T[n] and delete[] p correct. If you get it wrong,
- neither a compile-time nor a run-time error message will be generated by the
- compiler. Heap corruption is a likely result. Or worse. Your program will
- probably die.
-
- ==============================================================================
-
- [16.12] Can I drop the [] when deleteing array of some built-in type (char,
- int, etc)?
-
- No!
-
- Sometimes programmers think that the [] in the delete[] p only exists so the
- compiler will call the appropriate destructors for all elements in the array.
- Because of this reasoning, they assume that an array of some built-in type such
- as char or int can be deleted without the []. E.g., they assume the following
- is valid code:
-
- void userCode(int n)
- {
- char* p = new char[n];
- // ...
- delete p; // <-- ERROR! Should be delete[] p !
- }
-
- But the above code is wrong, and it can cause a disaster at runtime. In
- particular, the code that's called for delete p is operator delete(void*), but
- the code that's called for delete[] p is operator delete[](void*). The default
- behavior for the latter is to call the former, but users are allowed to replace
- the latter with a different behavior (in which case they would normally also
- replace the corresponding new code in operator new[](size_t)). If they
- replaced the delete[] code so it wasn't compatible with the delete code, and
- you called the wrong one (i.e., if you said delete p rather than delete[] p),
- you could end up with a disaster at runtime.
-
- ==============================================================================
-
- [16.13] After p = new Fred[n], how does the compiler know there are n objects
- to be destructed during delete[] p?
-
- Short answer: Magic.
-
- Long answer: The run-time system stores the number of objects, n, somewhere
- where it can be retrieved if you only know the pointer, p. There are two
- popluar techniques that do this. Both these techniques are in use by
- commercial grade compilers, both have tradeoffs, and neither is perfect. These
- techniques are:
-
- 3. Over-allocate the array and put n just to the left of the first Fred
- object[33.4].
-
- 4. Use an associative array with p as the key and n as the value[33.5].
-
- ==============================================================================
-
- [16.14] Is it legal (and moral) for a member function to say delete this?
-
- As long as you're careful, it's OK for an object to commit suicide (delete
- this).
-
- Here's how I define "careful":
-
- 1. You must be absolutely 100% positive sure that this object was allocated
- via new (not by new[], nor by placement new[11.10], nor a local object on
- the stack, nor a global, nor a member of another object; but by plain
- ordinary new).
-
- 2. You must be absolutely 100% positive sure that your member function will be
- the last member function invoked on this object.
-
- 3. You must be absolutely 100% positive sure that the rest of your member
- function (after the delete this line) doesn't touch any piece of this
- object (including calling any other member functions or touching any data
- members).
-
- 4. You must be absolutely 100% positive sure that no one even touches the this
- pointer itself after the delete this line. In other words, you must not
- examine it, compare it with another pointer, compare it with NULL, print
- it, cast it, do anything with it.
-
- Naturally the usual caveats apply in cases where your this pointer is a pointer
- to a base class when you don't have a virtual destructor[20.4].
-
- ==============================================================================
-
- [16.15] How do I allocate multidimensional arrays using new? [UPDATED!]
-
- [Recently rewritten and expanded with a rectangular-matrix case (on 1/97).]
-
- There are many ways to do this, depending on how flexible you want the array
- sizing to be. On one extreme, if you know all the dimensions at compile-time,
- you can allocate multidimensional arrays statically (as in C):
-
- class Fred { /*...*/ };
- void someFunction(Fred& fred);
-
- void manipulateArray()
- {
- const unsigned nrows = 10; // Num rows is a compile-time constant
- const unsigned ncols = 20; // Num columns is a compile-time constant
- Fred matrix[nrows][ncols];
-
- for (unsigned i = 0; i < nrows; ++i) {
- for (unsigned j = 0; j < ncols; ++j) {
- // Here's the way you access the (i,j) element:
- someFunction( matrix[i][j] );
-
- // You can safely "return" without any special delete code:
- if (today == "Tuesday" && moon.isFull())
- return; // Quit early on Tuesdays when the moon is full
- }
- }
-
- // No explicit delete code at the end of the function either
- }
-
- More commonly, the size of the matrix isn't known until run-time but you know
- that it will be rectangular. In this case you need to use the heap
- ("freestore"), but at least you are able to allocate all the elements in one
- freestore chunk.
-
- void manipulateArray(unsigned nrows, unsigned ncols)
- {
- Fred* matrix = new Fred[nrows * ncols];
-
- // Since we used a simple pointer above, we need to be VERY
- // careful to avoid skipping over the delete code.
- // That's why we catch all exceptions:
- try {
-
- // Here's how to access the (i,j) element:
- for (unsigned i = 0; i < nrows; ++i) {
- for (unsigned j = 0; j < ncols; ++j) {
- someFunction( matrix[i*ncols + j] );
- }
- }
-
- // If you want to quit early on Tuesdays when the moon is full,
- // make sure to do the delete along ALL return paths:
- if (today == "Tuesday" && moon.isFull()) {
- delete[] matrix;
- return;
- }
-
- // ...
-
- }
- catch (...) {
- // Make sure to do the delete when an exception is thrown:
- delete[] matrix;
- throw; // Re-throw the current exception
- }
-
- // Make sure to do the delete at the end of the function too:
- delete[] matrix;
- }
-
- Finally at the other extreme, you may not even be guaranteed that the matrix is
- rectangular. For example, if each row could have a different length, you'll
- need to allocate each row individually. In the following function, ncols[i] is
- the number of columns in row number i, where i varies between 0 and nrows-1
- inclusive.
-
- void manipulateArray(unsigned nrows, unsigned ncols[])
- {
- Fred** matrix = new Fred*[nrows];
- for (unsigned i = 0; i < nrows; ++i)
- matrix[i] = new Fred[ ncols[i] ];
-
- // Since we used a simple pointer above, we need to be VERY
- // careful to avoid skipping over the delete code.
- // That's why we catch all exceptions:
- try {
-
- // Here's how to access the (i,j) element:
- for (unsigned i = 0; i < nrows; ++i) {
- for (unsigned j = 0; j < ncols[i]; ++j) {
- someFunction( matrix[i][j] );
- }
- }
-
- // If you want to quit early on Tuesdays when the moon is full,
- // make sure to do the delete along ALL return paths:
- if (today == "Tuesday" && moon.isFull()) {
- for (unsigned i = nrows; i > 0; --i)
- delete[] matrix[i-1];
- delete[] matrix;
- return;
- }
-
- // ...
-
- }
- catch (...) {
- // Make sure to do the delete when an exception is thrown:
- for (unsigned i = nrows; i > 0; --i)
- delete[] matrix[i-1];
- delete[] matrix;
- throw; // Re-throw the current exception
- }
-
- // Make sure to do the delete at the end of the function too.
- // Note that deletion is the opposite order of allocation:
- for (i = nrows; i > 0; --i)
- delete[] matrix[i-1];
- delete[] matrix;
- }
-
- Note the funny use of matrix[i-1] in the deletion process. This prevents
- wrap-around of the unsigned value when i goes one step below zero.
-
- Finally, note that pointers and arrays are evil[21.5]. It is normally much
- better to encapsulate your pointers in a class that has a safe and simple
- interface. The following FAQ[16.16] shows how to do this.
-
- ==============================================================================
-
- [16.16] But the previous FAQ's code is SOOOO tricky and error prone! Isn't
- there a simpler way? [NEW!]
-
- [Recently created (on 1/97).]
-
- Yep.
-
- The reason the code in the previous FAQ[16.15] was so tricky and error prone
- was that it used pointers, and we know that pointers and arrays are evil[21.5].
- The solution is to encapsulate your pointers in a class that has a safe and
- simple interface. For example, we can define a FredMatrix class that handles a
- rectangular matrix so our user code will be vastly simplified when compared to
- the the rectangular matrix code from the previous FAQ[16.15]:
-
- // The code for class FredMatrix is shown below...
- void someFunction(Fred& fred);
-
- void manipulateArray(unsigned nrows, unsigned ncols)
- {
- FredMatrix matrix(nrows, ncols); // Construct a FredMatrix called matrix
-
- for (unsigned i = 0; i < nrows; ++i) {
- for (unsigned j = 0; j < ncols; ++j) {
- // Here's the way you access the (i,j) element:
- someFunction( matrix(i,j) );
-
- // You can safely "return" without any special delete code:
- if (today == "Tuesday" && moon.isFull())
- return; // Quit early on Tuesdays when the moon is full
- }
- }
-
- // No explicit delete code at the end of the function either
- }
-
- The main thing to notice is the lack of clean-up code. For example, there
- aren't any delete statements in the above code, yet there will be no memory
- leaks, assuming only that the FredMatrix destructor does its job correctly.
-
- Here's the FredMatrix code that makes the above possible:
-
- class FredMatrix {
- public:
- FredMatrix(unsigned nrows, unsigned ncols);
- // Throws a BadSize object if either size is zero
- class BadSize { };
-
- // Based on the Law Of The Big Three[25.8]:
- ~FredMatrix();
- FredMatrix(const FredMatrix& m);
- FredMatrix& operator= (const FredMatrix& m);
-
- // Access methods to get the (i,j) element:
- Fred& operator() (unsigned i, unsigned j);
- const Fred& operator() (unsigned i, unsigned j) const;
- // These throw a BoundsViolation object if i or j is too big
- class BoundsViolation { };
-
- private:
- Fred* data_;
- unsigned nrows_, ncols_;
- };
-
- inline Fred& Matrix::operator() (unsigned row, unsigned col)
- {
- if (row >= nrows_ || col >= ncols_) throw BoundsViolation();
- return data_[row*ncols_ + col];
- }
-
- inline Fred Matrix::operator() (unsigned row, unsigned col) const
- {
- if (row >= nrows_ || col >= ncols_) throw BoundsViolation();
- return data_[row*ncols_ + col];
- }
-
- FredMatrix::FredMatrix(unsigned nrows, unsigned ncols)
- : data_ (new Fred[nrows * ncols]),
- nrows_ (nrows),
- ncols_ (ncols)
- {
- if (nrows == 0 || ncols == 0)
- throw BadSize();
- }
-
- Matrix::~Matrix()
- {
- delete[] data_;
- }
-
- Note that the above FredMatrix class accomplishes two things: it moves some
- tricky memory management code from the user code (e.g., main()) to the class,
- and it reduces the overall bulk of program (e.g., assuming FredMatrix is even
- mildly reusable, moving complexity from the users of FredMatrix into FredMatrix
- itself is equivalent to moving complexity from the many to the few. And anyone
- who's seen Star Trek 3 knows that the good of the many outweighs the good of
- the few... or the one.
-
- ==============================================================================
-
- [16.17] Does C++ have arrays whose length can be specified at run-time?
-
- Yes, in the sense that STL[32.1] has a vector template that provides this
- behavior.
-
- No, in the sense that built-in array types need to have their length specified
- at compile time.
-
- Yes, in the sense that even built-in array types can specify the first index
- bounds at run-time. E.g., comparing with the previous FAQ, if you only need
- the first array dimension to vary then you can just ask new for an array of
- arrays, rather than an array of pointers to arrays:
-
- const unsigned ncols = 100; // ncols = number of columns in the array
-
- class Fred { /*...*/ };
-
- void manipulateArray(unsigned nrows) // nrows = number of rows in the array
- {
- Fred (*matrix)[ncols] = new Fred[nrows][ncols];
- // ...
- delete[] matrix;
- }
-
- You can't do this if you need anything other than the first dimension of the
- array to change at run-time.
-
- But please, don't use arrays unless you have to. Arrays are evil[21.5]. Use
- some object of some class if you can. Use arrays only when you have to.
-
- ==============================================================================
-
- [16.18] How can I force objects of my class to always be created via new rather
- than as locals or global/static objects?
-
- Use the Named Constructor Idiom[10.6].
-
- As usual with the Named Constructor Idiom, the constructors are all private: or
- protected:, and there are one or more public static create() methods (the
- so-called "named constructors"), one per constructor. In this case the
- create() methods allocate the objects via new. Since the constructors
- themselves are not public, there is no other way to create objects of the
- class.
-
- class Fred {
- public:
- // The create() methods are the "named constructors":
- static Fred* create() { return new Fred(); }
- static Fred* create(int i) { return new Fred(i); }
- static Fred* create(const Fred& fred) { return new Fred(fred); }
- // ...
-
- private:
- // The constructors themselves are private or protected:
- Fred();
- Fred(int i);
- Fred(const Fred& fred);
- // ...
- };
-
- Now the only way to create Fred objects is via Fred::create():
-
- main()
- {
- Fred* p = Fred::create(5);
- // ...
- delete p;
- }
-
- Make sure your constructors are in the protected: section if you expect Fred to
- have derived classes.
-
- Note also that you can make another class Wilma a friend[14] of Fred if you
- want to allow a Wilma to have a member object of class Fred, but of course this
- is a softening of the original goal, namely to force Fred objects to be
- allocated via new.
-
- ==============================================================================
-
- [16.19] How do I do simple reference counting?
-
- If all you want is the ability to pass around a bunch of pointers to the same
- object, with the feature that the object will automagically get deleted when
- the last pointer to it disappears, you can use something like the following
- "smart pointer" class:
-
- // Fred.h
-
- class FredPtr;
-
- class Fred {
- public:
- Fred() : count_(0) /*...*/ { } // All ctors set count_ to 0 !
- // ...
- private:
- friend FredPtr; // A friend class[14]
- unsigned count_;
- // count_ must be initialized to 0 by all constructors
- // count_ is the number of FredPtr objects that point at this
- };
-
- class FredPtr {
- public:
- Fred* operator-> () { return p_; }
- Fred& operator* () { return *p_; }
- FredPtr(Fred* p) : p_(p) { } // p must not be NULL
- ~FredPtr() { if (--p_->count_ == 0) delete p_; }
- FredPtr(const FredPtr& p) : p_(p.p_) { ++p_->count_; }
- FredPtr& operator= (const FredPtr& p)
- { // DO NOT CHANGE THE ORDER OF THESE STATEMENTS!
- // (This order properly handles self-assignment[12.1])
- ++p.p_->count_;
- if (--p_->count_ == 0) delete p_;
- p_ = p.p_;
- return *this;
- }
- private:
- Fred* p_; // p_ is never NULL
- };
-
- Naturally you can use nested classes to rename FredPtr to Fred::Ptr.
-
- Note that you can soften the "never NULL" rule above with a little more
- checking in the copy constructor, assignment operator, and destructor. If you
- do that, you might as well put a p_ != NULL check into the "*" and "->"
- operators (at least as an assert()). I would recommend against an
- operator Fred*() method, since that would let people accidentally get at the
- Fred*.
-
- If you want to be really safe, you can make all of Fred's constructors private,
- and for each constructor have a public (static) create() method. These
- create() methods would return a FredPtr rather than a Fred*. That way the only
- way anyone could create a Fred object would be to get a FredPtr
- ("Fred* p = new Fred()" would be replaced by "FredPtr p = Fred::create()").
- Thus no one could accidentally subvert the reference counted mechanism by
- saying getting a Fred*. For example, if Fred had a Fred::Fred() and a
- Fred::Fred(int i, int j), the changes to class Fred would be:
-
- class Fred {
- public:
- static FredPtr create() { return new Fred(); }
- static FredPtr create(int i, int j) { return new Fred(i,j); }
- // ...
- private:
- Fred();
- Fred(int i, int j);
- // ...
- };
-
- The end result is that you now have a way to use simple reference counting to
- provide "pointer semantics" for a given object. Users of your Fred class
- explicitly use FredPtr objects, which act more or less like Fred* pointers.
- The benefit is that users can make as many copies of their FredPtr "smart
- pointer" objects, and the pointed-to Fred object will automagically get deleted
- when the last such FredPtr object vanishes.
-
- If you'd rather give your users "reference semantics" rather than "pointer
- semantics," you can use reference counting to provide "copy on write"[16.20].
-
- ==============================================================================
-
- [16.20] How do I provide reference counting with copy-on-write semantics?
-
- The previous FAQ[16.19] a simple reference counting scheme that provided users
- with pointer semantics. This FAQ describes an approach that provides users
- with reference semantics.
-
- The basic idea is to allow users to think they're copying your Fred objects,
- but in reality the underlying implementation doesn't actually do any copying
- unless and until some user actually tries to modify the underlying Fred object.
-
- Class Fred::Data houses all the data that would normally go into the Fred
- class. Fred::Data also has an extra data member, count_, to manage the
- reference counting. Class Fred ends up being a "smart reference" that
- (internally) points to a Fred::Data.
-
- class Fred {
- public:
-
- Fred(); // A default constructor[10.4]
- Fred(int i, int j); // A normal constructor
-
- Fred(const Fred& f);
- Fred& operator= (const Fred& f);
- ~Fred();
-
- void sampleInspectorMethod() const; // No changes to this object
- void sampleMutatorMethod(); // Change this object
-
- // ...
-
- private:
-
- class Data {
- public:
- Data();
- Data(int i, int j);
-
- // Since only Fred can access a Fred::Data object,
- // you can make Fred::Data's data public if you want.
- // But if that makes you uncomfortable, make the data private
- // and make Fred a friend class[14] via friend Fred;
- // ...
-
- unsigned count_;
- // count_ must be initialized to 0 by all constructors
- // count_ is the number of Fred objects that point at this
- };
-
- Data* data_;
- };
-
- Fred::Data::Data() : count_(1) /*init other data*/ { }
- Fred::Data::Data(int i, int j) : count_(1) /*init other data*/ { }
-
- Fred::Fred() : data_(new Data()) { }
- Fred::Fred(int i, int j) : data_(new Data(i, j)) { }
-
- Fred::Fred(const Fred& f)
- : data_(f.data_)
- {
- ++ data_->count_;
- }
-
- Fred& Fred::operator= (const Fred& f)
- {
- // DO NOT CHANGE THE ORDER OF THESE STATEMENTS!
- // (This order properly handles self-assignment[12.1])
- ++ f.data_->count_;
- if (--data_->count_ == 0) delete data_;
- data_ = f.data_;
- return *this;
- }
-
- Fred::~Fred()
- {
- if (--data_->count_ == 0) delete data_;
- }
-
- void Fred::sampleInspectorMethod() const
- {
- // This method promises ("const") not to change anything in *data_
- // Other than that, any data access would simply use "data_->..."
- }
-
- void Fred::sampleMutatorMethod()
- {
- // This method might need to change things in *data_
- // Thus it first checks if this is the only pointer to *data_
- if (data_->counter > 1) {
- Data* d = new Data(*data_); // Invoke Fred::Data's copy ctor
- -- data_->counter_;
- data_ = d;
- }
- assert(data_->counter_ == 1);
-
- // Now the method proceeds to access "data_->..." as normal
- }
-
- If it is fairly common to call Fred's default constructor[10.4], you can avoid
- all those new calls by sharing a common Fred::Data object for all Freds that
- are constructed via Fred::Fred(). To avoid static initialization order
- problems, this shared Fred::Data object is created "on first use" inside a
- function. Here are the changes that would be made to the above code (note that
- the shared Fred::Data object's destructor is never invoked; if that is a
- problem, either hope you don't have any static initialization order problems,
- or drop back to the approach described above):
-
- class Fred {
- public:
- // ...
- private:
- // ...
- static Data* defaultData();
- };
-
- Fred::Fred() : data_(defaultData()) { }
-
- Fred::Data* Fred::defaultData()
- {
- static Data* p = NULL;
- if (p == NULL) {
- p = new Data();
- ++ p->count_; // Make sure it never goes to zero
- }
- return p;
- }
-
- Note: You can also provide reference counting for a hierarchy of classes[16.21]
- if your Fred class would normally have been a base class.
-
- ==============================================================================
-
- [16.21] How do I provide reference counting with copy-on-write semantics for a
- hierarchy of classes?
-
- The previous FAQ[16.20] presented a reference counting scheme that provided
- users with reference semantics, but did so for a single class rather than for a
- hierarchy of classes. This FAQ extends the previous technique to allow for a
- hierarchy of classes. The basic difference is that Fred::Data is now the root
- of a hierarchy of classes, which probably cause it to have some virtual[20]
- functions. Note that class Fred itself will still not have any virtual
- functions.
-
- The Virtual Constructor Idiom[20.5] is used to make copies of the Fred::Data
- objects. To select which derived class to create, the sample code below uses
- the Named Constructor Idiom[10.6], but other techniques are possible (a switch
- statement in the constructor, etc). The sample code assumes two derived
- classes: Der1 and Der2. Methods in the derived classes are unaware of the
- reference counting.
-
- class Fred {
- public:
-
- static Fred create1(String s, int i);
- static Fred create2(float x, float y);
-
- Fred(const Fred& f);
- Fred& operator= (const Fred& f);
- ~Fred();
-
- void sampleInspectorMethod() const; // No changes to this object
- void sampleMutatorMethod(); // Change this object
-
- // ...
-
- private:
-
- class Data {
- public:
- Data() : count_(1) { }
- virtual ~Data() { } // A virtual destructor[20.4]
- virtual Data* clone() const = 0; // A virtual constructor[20.5]
- virtual void sampleInspectorMethod() const = 0; // A pure virtual function[22.4]
- virtual void sampleMutatorMethod() = 0;
- private:
- unsigned count_; // count_ doesn't need to be protected
- };
-
- class Der1 : public Data {
- public:
- Der1(String s, int i);
- virtual void sampleInspectorMethod() const;
- virtual void sampleMutatorMethod();
- virtual Data* clone() const;
- // ...
- };
-
- class Der2 : public Data {
- public:
- Der2(float x, float y);
- virtual void sampleInspectorMethod() const;
- virtual void sampleMutatorMethod();
- virtual Data* clone() const;
- // ...
- };
-
- Fred(Data* data);
- // Creates a Fred smart-reference that owns *data
- // It is private to force users to use a createXXX() method
- // Requirement: data must not be NULL
-
- Data* data_; // Invariant: data_ is never NULL
- };
-
- Fred::Fred(Data* data) : data_(data) { assert(data != NULL); }
-
- Fred Fred::create1(String s, int i) { return Fred(new Der1(s, i)); }
- Fred Fred::create2(float x, float y) { return Fred(new Der2(x, y)); }
-
- Fred::Data* Fred::Der1::clone() const { return new Der1(*this); }
- Fred::Data* Fred::Der2::clone() const { return new Der2(*this); }
-
- Fred::Fred(const Fred& f)
- : data_(f.data_)
- {
- ++ data_->count_;
- }
-
- Fred& Fred::operator= (const Fred& f)
- {
- // DO NOT CHANGE THE ORDER OF THESE STATEMENTS!
- // (This order properly handles self-assignment[12.1])
- ++ f.data_->count_;
- if (--data_->count_ == 0) delete data_;
- data_ = f.data_;
- return *this;
- }
-
- Fred::~Fred()
- {
- if (--data_->count_ == 0) delete data_;
- }
-
- void Fred::sampleInspectorMethod() const
- {
- // This method promises ("const") not to change anything in *data_
- // Therefore we simply "pass the method through" to *data_:
- data_->sampleInspectorMethod();
- }
-
- void Fred::sampleMutatorMethod()
- {
- // This method might need to change things in *data_
- // Thus it first checks if this is the only pointer to *data_
- if (data_->counter > 1) {
- Data* d = data_->clone(); // The Virtual Constructor Idiom[20.5]
- -- data_->counter_;
- data_ = d;
- }
- assert(data_->counter_ == 1);
-
- // Now we "pass the method through" to *data_:
- data_->sampleInspectorMethod();
- }
-
- Naturally the constructors and sampleXXX methods for Fred::Der1 and Fred::Der2
- will need to be implemented in whatever way is appropriate.
-
- ==============================================================================
-
-